昨天我們已經快速建立TodoList的專案,並簡單的創造一個模版與切割好各個components。今天要來串接我們之前寫好的RESTfulAPI了。
[GET] /api/todos
[POST] /api/todos
[PUT] /api/todos/{id}
[DELETE] /api/todos/{id}
Components 元件圖

(1)首先,因為api網址為http://localhost:9100/,TodoList 網址為http://localhost:3000/互相溝通會有跨網域的問題,所以我們利用webpack提供的proxy將api的網址代理掉。
package.json
"proxy":"http://localhost:9100/api"
(2)準備todo list api 的CRUD吧,在src / service/ todos.js 與 src / service/ helper.js
src / service/ helper.js 實作一些API回應處理
export const handleResponse = (response) => {
    return response.text().then((text) => {
        const data = text && JSON.parse(text);
        if (!response.ok) {
            const errorLog = {
                status: response.status,
                code: (data && data.ErrorCode) || '',
                msg: (data && data.ErrorMessage) || response.statusText,
            };
            return Promise.reject(errorLog);
        }
        return data;
    });
};
撰寫CRUD的function
import { handleResponse } from './helper';
const getTodos =()=> {
    const requestOptions = {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
    };
    return fetch(`/todos`, requestOptions)
        .then(handleResponse)
        .then((todos) => {
            return todos;
        });
};
const createTodos =(data)=> {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    };
    return fetch(`/todos`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};
const updateTodos =(id, data)=> {
    const requestOptions = {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    };
    return fetch(`/todos/${id}`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};
const deleteTodos =(id)=> {
    const requestOptions = {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' }
    };
    return fetch(`/todos/${id}`, requestOptions)
        .then(handleResponse)
        .then((res) => {
            return res;
        });
};
export const todosService = {
    getTodos,
    createTodos,
    updateTodos,
    deleteTodos
};
(3)ToDo這個最外層的父組件,包含了3個子組件,分別是TitleBox, TodoForm與TodoItems,所以會在ToDo這一個組件放置要操作的todolist資料,並藉由props將資料及方法傳遞給子組件接收。
import React from 'react';
import TitleBox from '../components/TitleBox';
import TodoForm from '../components/TodoForm';
import TodoItems from '../components/TodoItems';
import { todosService } from '../service//todo.js';
import { useState, useEffect } from 'react';
const ToDoList = () => {
    const [todos, setTodos] = useState([]);
    useEffect(() => {
        // 頁面載入時,取得代辦事項列表
        todosService.getTodos().then((data) => {
            setTodos(data);
        });
    }, []);
    const handleAdd = (text) => {
	        // 接收從TodoForm 呼叫的handleAdd()方法
        const data = {
            task: text,
        };
        todosService.createTodos(data).then((res) => {
            data.id = res;
            data.status = res;
            todos.push(data);
            setTodos([...todos]);
        });
    };
    const handleUpdate = (id, data) => {
        // 接收從TodoItems 呼叫的handleUpdate()方法
        todosService.updateTodos(id, data).then((res) => {
            const mapTodos = todos.map((todo) => {
                if (todo.id === id) {
                    todo.status = data.status;
                }
                return todo;
            });
            setTodos(mapTodos);
        });
    };
    const handleDelete = (id) => {
				// 接收從TodoItems 呼叫的handleDelete()方法
        todosService.deleteTodos(id).then((res) => {
            todos.forEach((todo, index) => {
                if (todo.id === id) {
                    todos.splice(index, 1);
                }
            });
            setTodos([...todos]);
        });
    };
    return (
        <div className="container">
            <TitleBox />
            <div className="todo-box">
                <TodoForm handleAdd={handleAdd} />
                <TodoItems todos={todos} handleUpdate={handleUpdate} handleDelete={handleDelete} />
            </div>
        </div>
    );
};
export default ToDoList;
(4)接著實作TodoItems的列表顯示,在頁面載入時要呼叫[GET]/todos API取得列表,todos資料並透過props傳遞給子組件(TodoItems),子組件接收到,利用map方法將資料渲染出來。
import React from 'react';
const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
    const done = 2;
    const notDone = 1;
    return (
        <ul>
            {todos.map((todo, index) => (
                <li
                    className={todo.status === done ? 'checked' : ''}
                    key={index}
                    onClick={() => {
                        const data = {status: done};
                        if (todo.status === done) {
                            data.status = notDone;
                        }
                        handleUpdate(todo.id, data);
                    }}>
                    {todo.task} <span className="badge bg-red">生活</span>
                    <span className="close" onClick={(event)=> {
                        event.stopPropagation();
                        handleDelete(todo.id)
                        }}>X</span>
                </li>
            ))}
        </ul>
    );
};
export default TodoItems;
(5)新增代辦事項,由父組件(ToDo)傳遞一個handleAdd 方法讓TodoFrom 點擊新增時,可以呼叫此方法並call [POST] /todos ,更改代辦事項資料集。
import React from 'react';
import { useState } from 'react';
const TodoForm = ({handleAdd}) => {
    const [toDoText, setToDoText] = useState('');
    const onInputChange = (event) => {
        const value = event.target.value;
        setToDoText(value);
    }
    return (
    <div className="header">
        <input type="text" id="todoInput" name="todoInput" value={toDoText} placeholder="New Item..." onChange={onInputChange}/>
        <button type="submit" className="addBtn" onClick={()=> {handleAdd(toDoText)}}>
            Add
        </button>
    </div>
    )
}
export default TodoForm;
(5)更新代辦事項狀態,在TodoItems 接收從父組件傳來的handleUpdate()方法,點擊項目執行此方法,觸發到父組件呼叫[PUT] /todos/{id},待成功後整理資料並渲染在畫面上。
const TodoItems = ({ todos, handleUpdate, handleDelete }) => {
    const done = 2;
    const notDone = 1;
    return (
        <ul>
            {todos.map((todo, index) => (
                <li
                    className={todo.status === done ? 'checked' : ''}
                    key={index}
                    onClick={() => {
                        const data = {status: done};
                        if (todo.status === done) {
                            data.status = notDone;
                        }
			                  // 呼叫由props傳遞過來的handleUpdate()
                        handleUpdate(todo.id, data);
                    }}>
                    {todo.task} <span className="badge bg-red">生活</span>
                </li>
            ))}
        </ul>
    );
};
(5)刪除代辦事項,在TodoItems 接收從父組件傳來的handleDelete()方法,點擊「X」執行此方法,觸發到父組件呼叫[DELETE] /todos/{id},待成功後整理資料並渲染在畫面上。
<span
      className="close"
      onClick={(event) => {
          event.stopPropagation();
          handleDelete(todo.id);
      }}>
      X
</span>
